﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using TowerDefence.Tiles;
using GraphicsLib;

namespace TowerDefence
{
    public interface ITileLocator
    {
        ITile GetTile(int _x, int _y);
        ITile GetTile(Vector2 _point);
    }

    /// <summary>
    /// Represents the play area
    /// </summary>
    public class World : ITileLocator
    {
        private List<ITile> tiles;
        private IMap map;

        public int TileWidth { get { return this.map.TileWidth; } }
        public int TileHeight { get { return this.map.TileHeight; } }
        public int XTiles { get { return this.map.Width; } }
        public int YTiles { get { return this.map.Height; } }
        public int Width { get { return this.TileWidth * this.XTiles; } }
        public int Height { get { return this.TileHeight * this.YTiles; } }

        /// <summary>
        /// Creates a World, given an enumeration of tiles and a drawable Map
        /// </summary>
        public World(IEnumerable<ITile> _tiles, IMap _map)
        {
            this.tiles = _tiles.ToList();
            this.map = _map;
        }

        /// <summary>
        /// Add a tower to the world
        /// </summary>
        /// <param name="_tower">The tower to add</param>
        /// <param name="_position">The grid square to add it to</param>
        /// <returns>True if the tile at _position reports success</returns>
        public bool AddTower(ITower _tower, Vector2 _position)
        {
            var mountPoint = GetTile(_position) as IMountPoint;
            if (mountPoint == null) return false;
            return mountPoint.AddTower(_tower);
        }

        /// <summary>
        /// Gets the tile at a specific position in the world
        /// </summary>
        /// <param name="_x">Tile's x position</param>
        /// <param name="_y">Tile's y position</param>
        public ITile GetTile(int _x, int _y)
        {
            return GetTile(new Vector2(_x, _y));
        }

        /// <summary>
        /// Gets the tile at a specific position in the world
        /// </summary>
        /// <param name="_position">Tile's position</param>
        /// <returns>The tile if one exists at that position, null if it doesn't</returns>
        public ITile GetTile(Vector2 _position)
        {
            var result = from tile in this.tiles 
                         where tile != null && tile.Position == GetTileGridCoords(_position) 
                         select tile;
            try { return result.First(); }
            catch (InvalidOperationException) { return null; } // handle a null result set
        }

        /// <summary>
        /// Passes through to GetSurroundingTiles(Vector2 _position)
        /// </summary>        
        public Dictionary<Vector2, ITile> GetSurroundingTiles(int _x, int _y, int _distance)
        {
            return GetSurroundingTiles(new Vector2(_x, _y), _distance);
        }

        /// <summary>
        /// Gets the tiles in a square around a given position onscreen
        /// </summary>
        /// <param name="_position">Where the centre of the sqaure should be</param>
        /// <param name="_distance">How far you can "see" from the centre tile</param>
        /// <returns>
        /// A dictionary of tiles, keyed relative to the centre tile
        /// So for example, the tile above and to the left of the centre
        /// tile would be (-1, 1), the tile to the right (0, 1)
        /// 
        /// Since the requested tile could be on the edge of the level or on the
        /// corner, dictionary entries won't be created for these tiles. It's therefore
        /// up to the caller to handle KeyNotFound exceptions when querying this dictionary
        /// </returns>
        public Dictionary<Vector2, ITile> GetSurroundingTiles(Vector2 _position, int _distance)
        {          
            var result = new Dictionary<Vector2, ITile>();
            // top-left square in result grid
            var startPos = new Vector2(
                _position.X - (this.TileWidth * _distance),
                _position.Y - (this.TileHeight * _distance));
            startPos = GetTileGridCoords(startPos);
            int offsetX = -_distance;
            int offsetY = -_distance;
            int gridSide = (_distance * 2) + 1;
            
            int right = (int)(startPos.X + (this.TileWidth * gridSide));
            int bottom = (int)(startPos.Y + (this.TileHeight * gridSide));
            for (int y = (int)startPos.Y; y < bottom; y += this.TileHeight)
            {
                for (int x = (int)startPos.X; x < right; x += this.TileWidth)
                {
                    result.Add(new Vector2(offsetX, offsetY), GetTile(x, y));
                    offsetX += 1;
                }
                offsetY += 1;
                offsetX = -_distance;
            }
            return result;
        }

        /// <summary>
        /// Gets the grid coordinates of the containing tile
        /// </summary>
        /// <param name="_position">Position to look up</param>
        /// <returns>The grid coords of the containing tile</returns>
        public Vector2 GetTileGridCoords(Vector2 _position)
        {
            int x = this.TileWidth * ((int)_position.X / (int)this.TileWidth);
            int y = this.TileHeight * ((int)_position.Y / (int)this.TileHeight);
            return new Vector2(x, y);
        }

        public void DebugDraw(Graphics2D _graphics)
        {
            foreach (var tile in this.tiles)
            {
                tile.DebugDraw(_graphics);
            }
        }
    }
}
